﻿using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
using gixoom.Dom.Converters;
using System.Collections.ObjectModel;

namespace gixoom.Dom.Score
{
    internal class Sha512Hash: IScore
    {
        private byte[] bytes;
        private ReadOnlyCollection<byte> bytesReadOnly;
        private string hashString;
        private int? hashCode;  // hash code of current object
        private IHexDigitsStringToBytesConverter hexDigitsStringToBytesConverter;

        /// <summary>
        /// Creates Sha512Hash object calculating hash for the specified content bytes.
        /// </summary>
        /// <param name="content">content bytes</param>
        /// <param name="algorithm">algorithm for calculating SHA512 hash.</param>
        public Sha512Hash(byte[] content, SHA512 algorithm)
        {
            bytes = algorithm.ComputeHash(content);
            bytesReadOnly = Array.AsReadOnly(bytes);
        }

        /// <summary>
        /// Creates Sha512Hash object calculating hash for the specified region
        /// of the specified byte array.
        /// </summary>
        /// <param name="content">the input to create hash object for</param>
        /// <param name="offset">the offset into the byte array from which to begin using data</param>
        /// <param name="count">the number of bytes in the array to use as data</param>
        /// <param name="algorithm">the algorithm for calculating SHA512 hash.</param>
        public Sha512Hash(byte[] content, int offset, int count, SHA512 algorithm)
        {
            bytes = algorithm.ComputeHash(content, offset, count);
            bytesReadOnly = Array.AsReadOnly(bytes);
        }

        /// <summary>
        /// Creates Sha512Hash object converting the specified hash string to bytes (lazily).
        /// </summary>
        /// <param name="hashString">hash string</param>
        /// <param name="hexDigitsStringToBytesConverter">hexadecimal digits string to bytes converter</param>
        public Sha512Hash(string hashString,
            IHexDigitsStringToBytesConverter hexDigitsStringToBytesConverter)
        {
            this.hashString = hashString;
            this.hexDigitsStringToBytesConverter = hexDigitsStringToBytesConverter;
        }

        /// <summary>
        /// Creates Sha512Hash object from the specified hash bytes.
        /// </summary>
        /// <param name="bytes">hash bytes</param>
        public Sha512Hash(byte[] bytes)
        {
            this.bytes = bytes;
            bytesReadOnly = Array.AsReadOnly(bytes);
        }

        /// <summary>
        /// Gets read only collection of bytes of score.
        /// First converts hash string to bytes if bytes are not initialized.
        /// </summary>
        /// <value>read only collection of bytes of score.</value>
        public ReadOnlyCollection<byte> GetBytes()
        {
            if (bytes == null)
            {
                bytes = hexDigitsStringToBytesConverter.Convert(hashString);
                bytesReadOnly = Array.AsReadOnly(bytes);
            }

            return bytesReadOnly;
        }

        public override bool Equals(object obj)
        {
            var score = obj as Sha512Hash;
            var result = (score != null) && (ToString().Equals(score.ToString()));

            return result;
        }

        public override int GetHashCode()
        {
            if (!hashCode.HasValue)
            {
                hashCode = ToString().GetHashCode();
            }

            return hashCode.Value;
        }

        /// <summary>
        /// Converts score to string if hash string was not already initialized.
        /// </summary>
        /// <returns>string representation of score</returns>
        public override string ToString()
        {
            if (hashString == null)
            {
                StringBuilder hexDigits = new StringBuilder(bytes.Length << 1);

                foreach (byte value in bytes)
                {
                    hexDigits.AppendFormat(value.ToString("X2"));
                }

                hashString = hexDigits.ToString();
            }

            return hashString;
        }
    }
}
